import paramiko
import time
import logging
import sys
import os
import re
import threading
from datetime import datetime
import paramiko
from scp import SCPClient
import tkinter as tk
from tkinter import filedialog, ttk, scrolledtext, messagebox


def configure_openwrt(host, port, username, password, config_file):
    """
    OpenWrt自动化配置工具
    :param host:
    :param port:
    :param username:
    :param password:
    :param config_file: "E:\\file\\network_config_tool_TLS\\openwrt_commands.txt"
    """
    # 创建带日期的日志文件名（同一天的日志写入同一个文件）
    log_filename = f"openwrt_config_{datetime.now().strftime('%Y%m%d')}.log"

    # 设置日志
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    
    # 只保留TextHandler（如果存在）
    text_handlers = [h for h in logger.handlers if isinstance(h, TextHandler)]
    for handler in logger.handlers[:]:
        if handler not in text_handlers:
            logger.removeHandler(handler)
    
    # 创建文件处理器
    log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), log_filename)
    file_handler = logging.FileHandler(log_path, encoding='utf-8')
    file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
    logger.addHandler(file_handler)
    
    # 添加控制台处理器
    stream_handler = logging.StreamHandler(sys.stdout)
    stream_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
    logger.addHandler(stream_handler)
    
    # 如果在GUI模式下运行，确保已添加TextHandler
    if 'root' in locals() and hasattr(root, 'text_handler') and root.text_handler not in logger.handlers:
        logger.addHandler(root.text_handler)

    logging.info(f"OpenWrt自动化配置工具启动")
    logging.info(f"目标设备: {username}@{host}:{port}")
    logging.info(f"配置文件: {config_file}")

    # 规范化文件路径
    config_file = os.path.normpath(config_file)

    # 读取配置文件
    try:
        with open(config_file, 'r', encoding='utf-8') as f:
            commands = []
            for line in f:
                # 移除行尾注释和空白字符
                line = line.split('#')[0].strip()
                if line:
                    commands.append(line)

        logging.info(f"成功读取配置文件，共 {len(commands)} 条有效命令")
    except Exception as e:
        logging.error(f"配置文件读取失败: {str(e)}")
        return False

    # 创建SSH客户端
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    try:
        # 连接路由器
        logging.info(f"正在连接 {host}:{port}...")
        client.connect(
            hostname=host,
            port=port,
            username=username,
            password=password,
            timeout=15,
            look_for_keys=False
        )
        logging.info("SSH连接成功")

        # 创建远程目录
        try:
            stdin, stdout, stderr = client.exec_command('mkdir -p /usr/local/bin/')
            exit_status = stdout.channel.recv_exit_status()
            if exit_status == 0:
                logging.info("成功创建远程目录 /usr/local/bin/")
            else:
                logging.error(f"创建远程目录失败，退出码: {exit_status}")
                logging.error(f"错误输出: {stderr.read().decode('utf-8', 'ignore')}")
                return False
        except Exception as e:
            logging.error(f"创建远程目录时出错: {str(e)}")
            return False

        # 添加SCP文件传输
        local_yhjk_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'yhjk')
        if os.path.exists(local_yhjk_path):
            with SCPClient(client.get_transport()) as scp:
                logging.info(f"正在上传本地yhjk文件夹到远程/usr/local/bin/")
                scp.put(local_yhjk_path, remote_path='/usr/local/bin/', recursive=True)
        else:
            logging.error(f"本地yhjk文件夹不存在: {local_yhjk_path}")
            return False

        # 创建交互式shell
        shell = client.invoke_shell()
        shell.settimeout(30)  # 增加超时时间

        # 读取初始欢迎信息（动态等待）
        welcome_output = ""
        start_time = time.time()
        while time.time() - start_time < 2:  # 最多等待1秒
            if shell.recv_ready():
                welcome_output += shell.recv(4096).decode('utf-8', 'ignore')
                start_time = time.time()  # 重置计时器
            else:
                time.sleep(0.1)
        logging.debug(f"初始输出:\n{welcome_output}")

        # 过滤危险命令
        blacklist = {'groupadd', 'groupdel', 'groupmod', 'ifdown', 'ifup', 'killall', 'lvremove', 'mount', 'passwd', 'pkill', 'pvremove', 'reboot', 'route', 'service', 'shutdown', 'su', 'sysctl', 'systemctl', 'umount', 'useradd', 'userdel', 'usermod', 'vgremove'}
        filtered_commands = []
        failed_commands = []
        for cmd in commands:
            cmd_lower = cmd.strip().lower()
            if any(cmd_lower.startswith(b) for b in blacklist):
                logging.warning(f"警告: 跳过危险命令: {cmd}")
                failed_commands.append((cmd, "危险命令被跳过"))
            else:
                filtered_commands.append(cmd)

        if not filtered_commands and not failed_commands:
            logging.info("没有可执行的命令")
            return True

        # 逐条执行命令
        success_count = 0
        for i, cmd in enumerate(filtered_commands, 1):
            try:
                logging.info(f"[{i}/{len(filtered_commands)}] 正在执行: {cmd}")
                shell.send(cmd + '\n')
                
                # 动态等待时间（根据命令复杂度）
                base_wait = 0.2  # 进一步减少基础等待时间
                max_read_time = 3  # 减少常规命令读取超时
                if "opkg install" in cmd:
                    base_wait = 4.0  # 优化软件安装等待时间
                    max_read_time = 8
                elif "uci commit" in cmd or "service" in cmd:
                    base_wait = 1.0  # 配置操作等待时间
                    max_read_time = 5  # 减少配置操作读取超时
                
                # 合并等待与读取阶段
                output = ""
                start_time = time.time()
                max_total_time = base_wait + max_read_time
                
                while time.time() - start_time < max_total_time:
                    if shell.recv_ready():
                        output += shell.recv(4096).decode('utf-8', 'ignore')
                        # 检测命令提示符以提前退出
                        if output.endswith(('# ', '$ ', '> ')):
                            break
                    time.sleep(0.05)  # 缩短检查间隔
                
                clean_output = re.sub(r'^.*?' + re.escape(cmd) + r'\s*\n', '', output, count=1, flags=re.DOTALL).strip()
                
                if not clean_output:
                    clean_output = "无输出"
                
                if "not found" in clean_output or "ERROR" in clean_output.upper() or "FAILED" in clean_output.upper():
                    logging.error(f"命令执行失败: {cmd}")
                    logging.error(f"错误输出: {clean_output[:500]}{'...' if len(clean_output) > 500 else ''}")
                    failed_commands.append((cmd, clean_output))
                else:
                    success_count += 1
                    logging.debug(f"命令输出: {clean_output[:300]}{'...' if len(clean_output) > 300 else ''}")
            except Exception as e:
                logging.error(f"执行命令时出错: {cmd} - {str(e)}")
                failed_commands.append((cmd, str(e)))

        # 生成执行报告
        logging.info("\n" + "=" * 60)
        logging.info(f"配置完成! 成功命令: {success_count}/{len(commands)}")
        if failed_commands:
            logging.warning(f"失败命令: {len(failed_commands)} 条")
            for idx, (cmd, error) in enumerate(failed_commands, 1):
                logging.warning(f"{idx}. 命令: {cmd}")
                logging.warning(f"   错误: {error[:200]}{'...' if len(error) > 200 else ''}")
        else:
            logging.info("所有命令执行成功!")

        logging.info(f"详细日志已保存至: {log_filename}")
        logging.info("=" * 60)

        return len(failed_commands) == 0

    except paramiko.AuthenticationException:
        logging.error("认证失败，请检查用户名/密码")
    except paramiko.SSHException as e:
        logging.error(f"SSH连接错误: {str(e)}")
    except Exception as e:
        logging.error(f"未知错误: {str(e)}")
    finally:
        try:
            client.close()
            logging.info("SSH连接已关闭")
        except:
            pass


class TextHandler(logging.Handler):
    def __init__(self, text_widget):
        logging.Handler.__init__(self)
        self.text_widget = text_widget
        self.text_widget.config(state=tk.DISABLED)
        self.formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

    def emit(self, record):
        msg = self.format(record) + '\n'
        self.text_widget.config(state=tk.NORMAL)
        self.text_widget.insert(tk.END, msg)
        self.text_widget.see(tk.END)
        self.text_widget.config(state=tk.DISABLED)


class OpenWrtConfiguratorUI:
    def __init__(self, root):
        self.root = root
        self.root.title("OpenWrt 自动化配置工具")
        self.root.geometry("800x600")
        self.root.resizable(True, True)
        self.root.minsize(500, 400)

        self.configuring = False
        self.is_closing = False
        self.worker_threads = []

        # 监听窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

        # 创建主框架
        main_frame = ttk.Frame(root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 按钮框架
        self.btn_frame = ttk.Frame(main_frame, height=40)
        self.btn_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=5)
        self.btn_frame.pack_propagate(False)

        self.start_btn = ttk.Button(self.btn_frame, text="开始配置", command=self.start_configuration)
        self.start_btn.pack(side=tk.RIGHT, padx=5, pady=5)

        self.exit_btn = ttk.Button(self.btn_frame, text="退出", command=root.quit)
        self.exit_btn.pack(side=tk.RIGHT, padx=5, pady=5)

        # 连接参数框架
        conn_frame = ttk.LabelFrame(main_frame, text="连接参数", padding="5")
        conn_frame.pack(fill=tk.X, pady=(0, 5))

        # 主机
        ttk.Label(conn_frame, text="主机:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        self.host_entry = ttk.Entry(conn_frame, width=30)
        self.host_entry.grid(row=0, column=1, padx=5, pady=5, columnspan=1)
        self.host_entry.insert(0, "10.0.5.0")

        # 端口
        ttk.Label(conn_frame, text="端口:").grid(row=0, column=2, sticky="w", padx=5, pady=5)
        self.port_entry = ttk.Entry(conn_frame, width=10)
        self.port_entry.grid(row=0, column=3, padx=5, pady=5)
        self.port_entry.insert(0, "22")

        # 用户名
        ttk.Label(conn_frame, text="用户名:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        self.user_entry = ttk.Entry(conn_frame, width=30)
        self.user_entry.grid(row=1, column=1, padx=5, pady=5, columnspan=1)
        self.user_entry.insert(0, "root")

        # 密码
        ttk.Label(conn_frame, text="密码:").grid(row=1, column=2, sticky="w", padx=5, pady=5)
        self.pass_entry = ttk.Entry(conn_frame, show="*", width=30)
        self.pass_entry.grid(row=1, column=3, padx=5, pady=5)
        self.pass_entry.insert(0, "yhjk&1103")

        # 配置文件
        config_frame = ttk.LabelFrame(main_frame, text="配置文件", padding="5")
        config_frame.pack(fill=tk.X, pady=(0, 5))

        # 日志框
        log_frame = ttk.LabelFrame(main_frame, text="操作日志", padding="5")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 5))

        self.log_text = tk.Text(log_frame, height=3, wrap=tk.WORD)
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
        scrollbar = ttk.Scrollbar(log_frame, command=self.log_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text.config(yscrollcommand=scrollbar.set)
        self.config_file_entry = ttk.Entry(config_frame)
        self.config_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5)
        self.config_file_entry.insert(0, "E:\\file\\network_config_tool_TLS - 3.0\\openwrt_commands.txt")

        browse_btn = ttk.Button(config_frame, text="浏览...", command=self.browse_config_file)
        browse_btn.pack(side=tk.RIGHT, padx=5, pady=5)

        # 配置日志
        self.text_handler = TextHandler(self.log_text)
        logger = logging.getLogger()
        logger.addHandler(self.text_handler)
        logger.setLevel(logging.INFO)

    def browse_config_file(self):
        filename = filedialog.askopenfilename(
            title="选择配置文件",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if filename:
            self.config_file_entry.delete(0, tk.END)
            self.config_file_entry.insert(0, filename)

    def start_configuration(self):
        if self.configuring:
            messagebox.showwarning("警告", "配置正在进行中，请不要重复操作")
            return

        host = self.host_entry.get().strip()
        port = self.port_entry.get().strip()
        username = self.user_entry.get().strip()
        password = self.pass_entry.get().strip()
        config_file = self.config_file_entry.get().strip()

        # 验证输入
        if not all([host, port, username, config_file]):
            messagebox.showerror("错误", "请填写所有必填字段")
            return

        try:
            port = int(port)
            if port < 1 or port > 65535:
                raise ValueError
        except ValueError:
            messagebox.showerror("错误", "请输入有效的端口号")
            return

        if not os.path.exists(config_file):
            messagebox.showerror("错误", f"配置文件不存在: {config_file}")
            return

        self.configuring = True
        self.start_btn.config(state=tk.DISABLED)

        # 在新线程中运行配置
        thread = threading.Thread(target=self.run_configuration, args=(host, port, username, password, config_file))
        thread.daemon = True  # 设置为守护线程，主线程结束时自动终止
        self.worker_threads.append(thread)
        thread.start()

    def run_configuration(self, host, port, username, password, config_file):
        try:
            result = configure_openwrt(host, port, username, password, config_file)
            # 使用线程安全的方式更新GUI
            if not self.is_closing:
                self.root.after(0, lambda r=result: self._safe_configuration_complete(r))
        except Exception as e:
            # 使用线程安全的方式显示错误
            if not self.is_closing:
                self.root.after(0, lambda err=str(e): self._safe_show_error(err))
        finally:
            # 从活跃线程列表中移除
            if threading.current_thread() in self.worker_threads:
                self.worker_threads.remove(threading.current_thread())

    def _safe_configuration_complete(self, result):
        """线程安全的配置完成回调"""
        if not self.is_closing and self.root.winfo_exists():
            self.configuration_complete(result)

    def _safe_show_error(self, message):
        """线程安全的错误显示回调"""
        if not self.is_closing and self.root.winfo_exists():
            self.show_error(f"配置过程中发生错误: {message}")

    def configuration_complete(self, success):
        self.configuring = False
        self.start_btn.config(state=tk.NORMAL)

        if not self.is_closing:
            if success:
                messagebox.showinfo("成功", "OpenWrt配置已完成")
            else:
                messagebox.showerror("失败", "OpenWrt配置过程中出现错误，请查看日志了解详情")

    def show_error(self, message):
        self.configuring = False
        self.start_btn.config(state=tk.NORMAL)
        if not self.is_closing:
            messagebox.showerror("错误", message)


    def on_closing(self):
        """处理窗口关闭事件"""
        self.is_closing = True
        # 等待所有工作线程完成
        for thread in self.worker_threads[:]:
            if thread.is_alive():
                thread.join(1.0)  # 等待最多1秒
        # 关闭窗口
        self.root.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = OpenWrtConfiguratorUI(root)
    root.mainloop()
